﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;



namespace WowVoiceBox
{
    public enum SpeechCommandType
    {
        Macro,
        SingleKey
    }
    public class SpeechCommand
    {
        private string voiceCommand;
        public string VoiceCommand
        {
            get { return voiceCommand; }
            set { voiceCommand = value; }
        }

        private string commandString;
        public string CommandString
        {
            get { return commandString; }
            set { commandString = value; }
        }

        private int commandKey;
        public int CommandKey
        {
            get { return commandKey; }
            set { commandKey = value; }
        }

        private List<int> keyModifiers;
        public List<int> KeyModifiers
        {
            get { return keyModifiers; }
            set { keyModifiers = value; }
        }

        private bool acceptsDictation;
        public bool AcceptsDictation
        {
            get { return acceptsDictation; }
            set { acceptsDictation = value; }
        }

        private bool broadcast;
        public bool Broadcast
        {
            get { return broadcast; }
            set { broadcast = value; }
        }

        private SpeechCommandType commandType;
        public SpeechCommandType CommandType
        {
            get { return commandType; }
            set { commandType = value; }
        }

        public const int VK_SHIFT = 0x10;
        public const int VK_LSHIFT = 0xA0;
        public const int VK_RSHIFT = 0xA1;

        public const int VK_CONTROL = 0x11;
        public const int VK_LCONTROL = 0xA2;
        public const int VK_RCONTROL = 0xA3;

        public const int VK_ALT = 0x12;
        public const int VK_LALT = 0xA4;
        public const int VK_RALT = 0xA5;

        public SpeechCommand()
        {
            CommandKey = 0;
            CommandString = "";
            CommandType = SpeechCommandType.Macro;
            VoiceCommand = "( UNKNOWN )";
            keyModifiers = new List<int>();
            Broadcast = false;
            AcceptsDictation = false;
        }

        public SpeechCommand(XmlNode commandNode)
        {
            keyModifiers = new List<int>();

            // First get the voice command
            if (commandNode.Attributes["voice"] != null)
            {
                this.voiceCommand = commandNode.Attributes["voice"].Value;
            }
            else
            {
                throw new Exception("Error: Found XML Command element without voice attibute.\n\n" + commandNode.OuterXml);
            }

            // Get the command string, if there is one
            if (commandNode.SelectSingleNode("commandString") != null)
            {
                this.commandString = commandNode.SelectSingleNode("commandString").InnerText;
                this.CommandType = SpeechCommandType.Macro;
            }

            // Get the command key, if there is one
            // See the following link for the key codes (in hex)
            // http://msdn.microsoft.com/en-us/library/ms927178.aspx
            if (commandNode.SelectSingleNode("commandKey") != null)
            {
                if (commandString != null)
                {
                    throw new Exception("Error: Command \"" + voiceCommand + "\" must not have both a commandString and commandKey node.");
                }
                XmlNode keyNode = commandNode.SelectSingleNode("commandKey");
                this.commandKey = Int32.Parse(keyNode.InnerText, System.Globalization.NumberStyles.HexNumber);
                if (commandKey < 0x08 || commandKey > 0xFF)
                {
                    throw new Exception("Error: CommandKey for command \"" + voiceCommand + "\" is outside the valid range.");
                }
                this.CommandType = SpeechCommandType.SingleKey;
            }
            else  // Make sure we had either command key or command string
            {  
                if (commandString == null)
                    throw new Exception("Error: Command \"" + voiceCommand + "\" must have either a commandString or commandKey node.");
            }

            // make sure command key is in the valid range, if we're using it

            // Determine if this command accepts dictation
            acceptsDictation = (commandType == SpeechCommandType.SingleKey) ? false : commandString.Contains("{0}");

            // Determine if we should broadcast this command
            if (commandNode.Attributes["broadcast"] != null)
            {
                this.Broadcast = true;
            }
            else
            {
                this.Broadcast = false;
            }

            // Set up any modifier keys
            XmlNodeList modifierNodes = commandNode.SelectNodes("modifier");
            
            foreach (XmlNode node in modifierNodes)
            {
                switch (node.InnerText.ToUpper())
                {
                    case "SHIFT":
                        if (keyModifiers.Contains(VK_SHIFT))
                        {
                            throw new Exception("Error: Dupliacate SHIFT modifier found in command element \"" + voiceCommand + "\"");
                        }
                        keyModifiers.Add(VK_SHIFT);
                        break;
                    case "LSHIFT":
                        if (keyModifiers.Contains(VK_LSHIFT))
                        {
                            throw new Exception("Error: Dupliacate LSHIFT modifier found in command element \"" + voiceCommand + "\"");
                        }
                        keyModifiers.Add(VK_LSHIFT);
                        break;
                    case "RSHIFT":
                        if (keyModifiers.Contains(VK_RSHIFT))
                        {
                            throw new Exception("Error: Dupliacate RSHIFT modifier found in command element \"" + voiceCommand + "\"");
                        }
                        keyModifiers.Add(VK_RSHIFT);
                        break;

                    case "CTRL":
                        if (keyModifiers.Contains(VK_CONTROL))
                        {
                            throw new Exception("Error: Dupliacate CTRL modifier found in command element \"" + voiceCommand + "\"");
                        }
                        keyModifiers.Add(VK_CONTROL);
                        break;
                    case "LCTRL":
                        if (keyModifiers.Contains(VK_LCONTROL))
                        {
                            throw new Exception("Error: Dupliacate LCTRL modifier found in command element \"" + voiceCommand + "\"");
                        }
                        keyModifiers.Add(VK_LCONTROL);
                        break;
                    case "RCTRL":
                        if (keyModifiers.Contains(VK_RCONTROL))
                        {
                            throw new Exception("Error: Dupliacate RCTRL modifier found in command element \"" + voiceCommand + "\"");
                        }
                        keyModifiers.Add(VK_RCONTROL);
                        break;

                    case "ALT":
                        if (keyModifiers.Contains(VK_ALT))
                        {
                            throw new Exception("Error: Dupliacate ALT modifier found in command element \"" + voiceCommand + "\"");
                        }
                        keyModifiers.Add(VK_ALT);
                        break;
                    case "LALT":
                        if (keyModifiers.Contains(VK_LALT))
                        {
                            throw new Exception("Error: Dupliacate LALT modifier found in command element \"" + voiceCommand + "\"");
                        }
                        keyModifiers.Add(VK_LALT);
                        break;
                    case "RALT":
                        if (keyModifiers.Contains(VK_RALT))
                        {
                            throw new Exception("Error: Dupliacate RALT modifier found in command element \"" + voiceCommand + "\"");
                        }
                        keyModifiers.Add(VK_RALT);
                        break;
                    default:
                        throw new Exception("Error: Invalid modifier in command node \"" + voiceCommand + "\"");
                }
            }
        }

        // The VoiceCommand attribute should be the string representation of a command
        public override String ToString()
        {
            return voiceCommand;
        }


        // This function returns this command as an XML node, to be used for saving the commands.
        public XmlNode ToXmlNode(XmlDocument parentDoc)
        {
            XmlNode newNode = parentDoc.CreateElement("command");

            // The voice command attribute
            XmlAttribute voiceCmdAtrb = parentDoc.CreateAttribute("voice");
            voiceCmdAtrb.Value = this.VoiceCommand;
            newNode.Attributes.Append(voiceCmdAtrb);

            // command string or key
            if (this.CommandType == SpeechCommandType.SingleKey)
            {
                XmlNode keyNode = parentDoc.CreateElement("commandKey");
                keyNode.InnerText = String.Format("{0:X}", this.CommandKey);

                newNode.AppendChild(keyNode);

                // Add any key modifiers
                foreach (int modifier in this.KeyModifiers)
                {
                    XmlNode modifierNode = parentDoc.CreateElement("modifier");
                    switch (modifier)
                    {
                        case VK_ALT:
                            modifierNode.InnerText = "ALT";
                            break;
                        case VK_CONTROL:
                            modifierNode.InnerText = "CTRL";
                            break;
                        case VK_SHIFT:
                            modifierNode.InnerText = "SHIFT";
                            break;
                    }
                    newNode.AppendChild(modifierNode);
                }
            }
            else // Add the command string
            {
                XmlNode stringNode = parentDoc.CreateElement("commandString");
                stringNode.InnerText = this.CommandString;
                newNode.AppendChild(stringNode);
            }

            // Add broadcast attribute
            if (this.Broadcast)
            {
                XmlAttribute broadcastAtrb = parentDoc.CreateAttribute("broadcast");
                broadcastAtrb.Value = "1";
                newNode.Attributes.Append(broadcastAtrb);
            }

            return newNode;
        }
    }
}
